package com.dragon.utils;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.IOException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RSA非对称加密工具类
 *
 * @author: dragon
 * @version: V1.0.0-RELEASE
 * @date: 2019/03/30 20:01
 */

public class RSAUtils {


    /**
     * 算法名称
     */
    private static final String ALGORITHM = "RSA";

    /**
     * 密钥长度
     */
    private static final int KEY_SIZE = 2048;

    /**
     * 签名算法
     */
    private static final String SIGNATURE_ALGORITHM = "Sha1WithRSA";


    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchPaddingException, SignatureException {
        // 创建公钥保存的文件路径
        File publicKeyFile = new File("publicKey.txt");
        // 创建私钥保存的文件路径
        File privateKeyFile = new File("privateKey.txt");
        // 生成RSA公私钥对
        generateRSAKeyPair(publicKeyFile, privateKeyFile);
        // 生成公钥对象
        PublicKey publicKey = generatePublicKey(publicKeyFile);
        // 生成私钥对象
        PrivateKey privateKey = generatePrivateKey(privateKeyFile);

        String s = "dragon helloworld";
//		byte[] encryptBytes = encrypt(s.getBytes(), publicKey);
//		System.out.println(new String(encryptBytes));
//		byte[] decryptBytes = decrypt(encryptBytes, privateKey);
//		System.out.println(new String(decryptBytes));

        // 对数据进行签名
        byte[] signBytes = sign(s.getBytes(), privateKey);
        // 验签
        boolean verify = verify(signBytes, publicKey);
        System.out.println("验签的结果:" + verify);

    }

    /**
     * 生成RSA公私钥对
     *
     * @param publicKeyFile  保存公钥的文件地址
     * @param privateKeyFile 保存私钥的文件地址
     * @return void
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    public static void generateRSAKeyPair(File publicKeyFile, File privateKeyFile) throws NoSuchAlgorithmException, IOException {
        // 获得指定算法的密钥对生成器
        KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM);
        // 初始化密码生成器(指定密钥长度,使用默认的安全随机数源)
        generator.initialize(KEY_SIZE);
        // 随机生成一对密钥(包括公钥与私钥)
        KeyPair keyPair = generator.generateKeyPair();
        // 获取公钥
        PublicKey publicKey = keyPair.getPublic();
        // 保存公钥到指定的文件中
        saveKeyForEncodeBase64(publicKey, publicKeyFile);
        // 获取私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 保存私钥到指定的文件中
        saveKeyForEncodeBase64(privateKey, privateKeyFile);
    }

    /**
     * 将key以Base64进行编码,保存到指定的文件中去
     *
     * @param key     钥匙对象
     * @param keyFile 保存到指定的文件
     * @return void
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    private static void saveKeyForEncodeBase64(Key key, File keyFile) throws IOException {
        // 获取key的编码
        byte[] keyEncodedBytes = key.getEncoded();
        // 创建Base64编码对象
        Base64.Encoder encoder = Base64.getEncoder();
        // 将key转换为Base64的编码格式
        String keyBase64 = encoder.encodeToString(keyEncodedBytes);
        // 将key写入到指定的文件中去
        IOUtils.writeFile(keyFile, keyBase64);
    }

    /**
     * 生成公钥对象
     *
     * @param publicKeyFile 公钥的文件地址
     * @return java.security.PublicKey
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    public static PublicKey generatePublicKey(File publicKeyFile) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        // 从公钥保存的文件中读取Base64文本
        String publicKeyBase64 = IOUtils.readFile(publicKeyFile);
        // 创建Base64编码对象
        Base64.Decoder decoder = Base64.getDecoder();
        // 把公钥的Base64文本转换为 已编码的公钥bytes
        byte[] encPubKey = decoder.decode(publicKeyBase64);
        // 创建已编码公钥规格
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(encPubKey);
        // 获取指定算法的密钥工厂, 根据 已编码的公钥规格, 生成公钥对象
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);

        return keyFactory.generatePublic(x509EncodedKeySpec);
    }

    /**
     * 生成私钥对象
     *
     * @param privateKeyFile 私钥存储的文件地址
     * @return java.security.PrivateKey
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    public static PrivateKey generatePrivateKey(File privateKeyFile) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        // 从公钥保存的文件中读取Base64文本
        String privateKeyFileBase64 = IOUtils.readFile(privateKeyFile);
        // 创建Base64编码对象
        Base64.Decoder decoder = Base64.getDecoder();
        // 把公钥的Base64文本转换为 已编码的公钥bytes
        byte[] encPrivateKey = decoder.decode(privateKeyFileBase64);
        // 创建已编码的私钥规格
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(encPrivateKey);
        // 获取指定算法的密钥工厂
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);

        return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
    }

    /**
     * 公钥加密
     *
     * @param plainData 需要加密的数据
     * @param pubKey    公钥对象
     * @return byte[]
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    public static byte[] encrypt(byte[] plainData, PublicKey pubKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // 获取指定算法的密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 初始化密码器（公钥加密模型）
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);

        // 加密数据, 返回加密后的密文
        return cipher.doFinal(plainData);
    }

    /**
     * 私钥解密
     *
     * @param cipherData 需要解密的数据
     * @param priKey     私钥对象
     * @return byte[]
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    public static byte[] decrypt(byte[] cipherData, PrivateKey priKey) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
        // 获取指定算法的密码器
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        // 初始化密码器（私钥解密模型）
        cipher.init(Cipher.DECRYPT_MODE, priKey);

        // 解密数据, 返回解密后的明文
        return cipher.doFinal(cipherData);
    }

    /**
     * 用私钥对数据进行签名
     *
     * @param data       待签名数据
     * @param privateKey 私钥
     * @return byte[]
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    public static byte[] sign(byte[] data, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        // 根据指定算法得到签名工具
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 用私钥初始化签名工具
        signature.initSign(privateKey);
        // 添加要签名的数据
        signature.update(data);

        return signature.sign();
    }

    /**
     * 用公钥对数据进行验签
     *
     * @param data      需要验签的数据
     * @param publicKey 公钥对象
     * @return boolean
     * @author dragon
     * @version V1.0.0-RELEASE
     * @date 2019/03/31
     */
    public static boolean verify(byte[] data, PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        // 根据指定算法得到签名工具
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 用公钥初始化签名工具
        signature.initVerify(publicKey);
        /*
         * 校验数据的签名信息是否正确,
         * 如果返回 true, 说明该数据的签名信息来自该公钥对应的私钥,
         * 同一个私钥的签名, 数据和签名信息一一对应, 只要其中有一点修改, 则用公钥无法校验通过,
         * 因此可以用私钥签名, 然后用公钥来校验数据的完整性与签名者（所有者）
         */
        boolean verify = signature.verify(data);

        return verify;
    }


}
